
/*
	HPET_main.c

	The main source for the system HPET driver routines.
	This source contains a single routine to initialise a system HPET for the scheduler timer.

	It should be noted that the system expects a HPET timer to fire an IRQ on vector 49 at 1000Hz periodic.

	Author: Aidan Goddard 17/10/13
*/

#include"../general/driver_error_codes.h"
#include"../../klib/headers/ktypes.h"
#include"../../klib/headers/klib_IRQ_control.h"
#include"../../klib/headers/klib_ACPI_tables.h"
#include"../../klib/headers/klib_system_timers.h"

#include"../../setup/headers/printf.h"
//#define DEBUG_MODE

// control variables
static uint32_t system_timer_GSI;

// pause & unpause functions (placeholders - do nothing atm)
static void pause()
{
}

static void unpause()
{
}

// main init routine
uint32_t HPET_INIT(uint32_t *error)
{
	// register with the system
	uint32_t sch_error = __KLIB__RegisterSchedulerTimer(&pause, &unpause);
	if(sch_error != SCHEDULER_TIMER_SUCCESS)
	{
		*error = sch_error;
		return DRIVER_STARTUP_FAIL;
	}

	// frequency to use
	uint64_t frequency = 1000;

	// first need to get the HPET table from the ACPI tables
	ACPI_HPET *hpet_table = __KLIB__ACPIGetHPETBase();
	if(hpet_table == NULL)
	{
		*error = 0xffffffff;
		__KLIB__UnregisterSchedulerTimer();
		return DRIVER_STARTUP_FAIL;
	}

	// get base address of HPET chip
	volatile uint64_t *hpet = (uint64_t*)hpet_table->base_address;

	#ifdef DEBUG_MODE
	printf("\n HPET table at 0x%a length %u", hpet_table, hpet_table->length);
	printf("\n HPET %u located at 0x%a", hpet_table->HPET_number, hpet_table->base_address);
	#endif

	// get HPET features
	__asm__ __volatile__("mfence":::"memory");
	uint64_t features = hpet[0];
	__asm__ __volatile__("mfence":::"memory");

	#ifdef DEBUG_MODE
	printf("\n HPET features: 0x%x", features);
	#endif

	uint64_t revision, timer_count, counter_size, legacy_replacement_capable, vendor_id, counter_clock_period;
	revision = features & 0xff;
	timer_count = ((features >> 8) & 0x1f) + 1;
	counter_size = (features >> 13) & 1;
	legacy_replacement_capable = (features >> 15) & 1;
	vendor_id = (features >> 16) & 0xffff;
	counter_clock_period = (features >> 32) & 0xffffffff;

	#ifdef DEBUG_MODE
	printf("\n HPET revision %u timer count %u counter size %u", revision, timer_count, counter_size);
	printf("\n HPET legacy replacement capable %u vendor id %u", legacy_replacement_capable, vendor_id);
	printf("\n HPET counter clock period %u femptoseconds", counter_clock_period);
	#endif

	// check revision is OK
	if(revision == 0)
	{
		*error = 0xfffffffe;
		__KLIB__UnregisterSchedulerTimer();
		return DRIVER_STARTUP_FAIL;
	}

	// calculate number of ticks required
	uint64_t fseconds_for_frequency = 1000000000000000 / frequency;
	uint64_t required_ticks = fseconds_for_frequency / counter_clock_period;

	#ifdef DEBUG_MODE
	printf("\n HPET required ticks for %uHz = %u", frequency, required_ticks);
	#endif

	// disable main counter + disable legacy replacement route
	__asm__ __volatile__("mfence":::"memory");
	uint64_t general_config = hpet[2];
	__asm__ __volatile__("mfence":::"memory");
	general_config = general_config & ((uint64_t)~3);
	__asm__ __volatile__("mfence":::"memory");
	hpet[2] = general_config;
	__asm__ __volatile__("mfence":::"memory");

	// zero main counter value register
	__asm__ __volatile__("mfence":::"memory");
	hpet[30] = 0;
	__asm__ __volatile__("mfence":::"memory");

	// find a timer capable of periodic mode
	int periodic_capable_timer = 0xffffffff;
	int periodic_capable_timer_index = 0;
	int timer_counter = 0;
	int i;
	for(i = (0x100 / 8); i < ((0x20 * timer_count) + 0x100) / 8; i += 4)
	{
		// read in value from timer config & capabilities register
		__asm__ __volatile__("mfence":::"memory");
		uint64_t timer_config_cap = hpet[i];
		__asm__ __volatile__("mfence":::"memory");

		// check for periodic support (bit 4 == 1)
		timer_config_cap = (timer_config_cap >> 4) & 1;
		if(timer_config_cap > 0)
		{
			periodic_capable_timer = timer_counter;
			periodic_capable_timer_index = i;
			break;
		}

		// this counter does not support periodic mode
		// move on to next one
		timer_counter++;
	}

	// check we found one
	if(periodic_capable_timer == 0xffffffff)
	{
		*error = 0xfffffffd;
		__KLIB__UnregisterSchedulerTimer();
		return DRIVER_STARTUP_FAIL;
	}

	// found periodic capable timer
	__asm__ __volatile__("mfence":::"memory");
	uint64_t periodic_capable_timer_features = hpet[periodic_capable_timer_index];
	__asm__ __volatile__("mfence":::"memory");

	#ifdef DEBUG_MODE
	printf("\n HPET timer %u (register offset %u) is periodic capable", periodic_capable_timer, periodic_capable_timer_index);
	printf("\n HPET timer %u features 0x%a", periodic_capable_timer, periodic_capable_timer_features);
	#endif

	// check which IRQ delivery method is supported (MSI is prefered, but IOAPIC is also supported)
	uint64_t config_value = 0x4c;		// periodic mode + enable + timer value set
	uint64_t MSI_cap = (periodic_capable_timer_features >> 15) & 1;
	if(MSI_cap > 0)
	{
		// get MSI message destination field
		uint32_t message_dest = 0;
		__KLIB__GetMSIDestination(&message_dest);

		// generate MSI message
		uint64_t FSB_address_message = message_dest;
		FSB_address_message = FSB_address_message << 32;
		FSB_address_message += 49;

		#ifdef DEBUG_MODE
		printf("\n using MSI IRQ routing with address + message: 0x%a", FSB_address_message);
		#endif

		__asm__ __volatile__("mfence":::"memory");
		hpet[periodic_capable_timer_index + 2] = FSB_address_message;
		__asm__ __volatile__("mfence":::"memory");

		// set the config to enable the FSB delivery mode
		config_value = config_value | (1 << 14);

	}
	else
	{
		// configure the HPET to use the IOAPIC routing method
		// first find which lines the device is capable of using (note: device MUST NOT be assigned to GSI 1 or 9 - PS2 keyboard and ACPI SCI)
		uint64_t line_bitmap = periodic_capable_timer_features >> 32;
		uint64_t GSI = 1; // 1 is a not allowed value so if it defaults to this, there are no valid lines
		uint64_t key = 0; // the GSI key

		// go through the bitmap to find a valid line
		for(i = 0; i < 32; i++)
		{
			// get the bit for this line
			uint32_t bit = line_bitmap & 1;
			line_bitmap = line_bitmap >> 1;

			// check the current line number for validity
			if(i == 1 || i == 9)
			{
				continue;
			}

			// line number is valid, check the bit
			if(bit == 1)
			{
				// line is available, attempt to reserve it
				GSI = i;
				if(__KLIB__GSIReserve(GSI, &key) == IRQ_SUCCESS)
				{
					break;
				}
			}

		}

		// check for illegal default line number
		if(GSI == 1)
		{
			// line number is illegal. halt on error
			*error = 0xfffffffc;
			__KLIB__UnregisterSchedulerTimer();
			return DRIVER_STARTUP_FAIL;
		}

		// have legal line number, configure the HPET to use it

		#ifdef DEBUG_MODE
		printf("\n HPET using IOAPIC IRQ routing to line %u", GSI);
		#endif

		config_value = config_value | (GSI << 9);

		// tell the IOAPIC to forward the GSI
		uint32_t IRQ_error = __KLIB__IOAPIC_manual_reg_write(GSI, key, 49, IRQ_IN_POLARITY_HIGH, IRQ_IN_TRIGGER_MODE_EDGE);
		if(IRQ_error != IRQ_SUCCESS)
		{
			*error = IRQ_error;
			__KLIB__UnregisterSchedulerTimer();
			return DRIVER_STARTUP_FAIL_IOAPIC;
		}

		#ifdef DEBUG_MODE
		printf("\n writing config value 0x%a", config_value);
		#endif

	}

	// write the configuration register
	__asm__ __volatile__("mfence":::"memory");
	hpet[periodic_capable_timer_index] = config_value;
	__asm__ __volatile__("mfence":::"memory");

	// configure the timer for the required frequency
	// write the necessary number of ticks to the comparitor register
	__asm__ __volatile__("mfence":::"memory");
	hpet[periodic_capable_timer_index + 1] = required_ticks;
	__asm__ __volatile__("mfence":::"memory");

	// write the global enable bit
	__asm__ __volatile__("mfence":::"memory");
	general_config = hpet[2];
	__asm__ __volatile__("mfence":::"memory");
	general_config = general_config | 1;
	__asm__ __volatile__("mfence":::"memory");
	hpet[2] = general_config;
	__asm__ __volatile__("mfence":::"memory");

	#ifdef DEBUG_MODE
	__asm__ __volatile__("mfence":::"memory");
	config_value = hpet[periodic_capable_timer_index];
	__asm__ __volatile__("mfence":::"memory");
	printf("\n reading back config value 0x%a", config_value);
	#endif


	// finished
	return DRIVER_STARTUP_SUCCESS;
}
